//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<system_console.cpp>
///	@path	~/src/lib/system/
///	@date	2007/11/12
///	@desc	Customized system output console.

#include "config/config.h"

#include "lib/utilities/debug.h"
#include "lib/system/system_console.h"

namespace xeres {

	// ctor
	SystemConsole::SystemConsole( void )
		: m_hConsole( NULL )
		, m_hWindow( NULL )
		, m_buffer( NULL )
		, m_width( 0 )
		, m_height( 0 )
	{
	}

	// dtor
	SystemConsole::~SystemConsole( void )
	{
		if( m_hConsole )
			Finalize();

		if( m_buffer )
			free( m_buffer );
	}

	// get console instance
	SystemConsole& SystemConsole::instance( void )
	{
		static SystemConsole s_systemConsole;
		return s_systemConsole;
	}

	// initialize console
	void SystemConsole::Initialize( int width , int height )
	{
		LockGuard<ThreadLock> s_consoleLock( m_lock );

		if( m_hConsole != NULL )
		{
			TRACE_ERROR( _S("SystemConsole::Initialize: System console is initialized.") );
			return;
		}

		if( !::AllocConsole() )
		{
			__win32_error( _S("SystemConsole::Initialize: Failed to create console : %s.") );
			return;
		}

		// create buffer
		m_hConsole = ::CreateConsoleScreenBuffer(
			GENERIC_READ | GENERIC_WRITE ,
			FILE_SHARE_READ ,
			NULL ,
			CONSOLE_TEXTMODE_BUFFER ,
			NULL );

		if( m_hConsole == INVALID_HANDLE_VALUE )
		{
			m_hConsole = NULL;
			__win32_error( _S("SystemConsole::Initialize: Failed to create console buffer: %s.") );
			return;
		}

		// check minimum width & height
		const int console_min_x = GetSystemMetrics( SM_CXMIN );
		const int console_min_y = GetSystemMetrics( SM_CYMIN );

		if( width < console_min_x )
		{
			TRACE_WARNING( _S("SystemConsole::Initialize: Console width is too small.") );
			width = console_min_x;
		}
		if( height < console_min_y )
		{
			TRACE_WARNING( _S("SystemConsole::Initialize: Console height is too small.") );
			height = console_min_y;
		}

		COORD size = { width , height };

		// set buffer
		if( !::SetConsoleScreenBufferSize(
			m_hConsole ,
			size ) )
		{
			::FreeConsole();
			m_hConsole = NULL;
			__win32_error( _S("SystemConsole::Initialize: Failed to set console buffer : %s.") );
			return;
		}

		// set active screen buffer
		if( !::SetConsoleActiveScreenBuffer( m_hConsole ) )
		{
			::FreeConsole();
			m_hConsole = NULL;
			__win32_error( _S("SystemConsole::Initialize: Failed to create console : %s.") );
			return;
		}

		// set window fit
		SMALL_RECT rect = { 0 , 0 , width - 1 , height - 1 };

		if( !::SetConsoleWindowInfo( m_hConsole , TRUE , &rect ) )
		{
			::FreeConsole();
			m_hConsole = NULL;
			__win32_error( _S("SystemConsole::Initialize: Failed to set window size: %s.") );
			return;
		}

		m_hWindow = ::GetConsoleWindow();

		if( m_buffer )
			free( m_buffer );

		m_width = width;
		m_height = height;
		// create local buffer
		m_buffer = static_cast<CHAR_INFO*>( malloc( sizeof(CHAR_INFO) * m_width * m_height ) );

		for( int i = 0 ; i < m_width * m_height ; ++i )
		{
			m_buffer[i].Char.UnicodeChar = ' ';
			m_buffer[i].Attributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
		}

		COORD coord = { 0 , 0 };
		SMALL_RECT region = { 0 , 0 , m_width , m_height };
		if( !::WriteConsoleOutput(
			m_hConsole ,
			m_buffer ,
			size ,
			coord ,
			&region ) )
		{
			::FreeConsole();
			m_hConsole = NULL;
			__win32_error( _S("SystemConsole::Initialize: Failed to write console : %s.") );
			return;
		}
	}

	// finalize
	void SystemConsole::Finalize( void )
	{
		LockGuard<ThreadLock> s_consoleLock( m_lock );

		if( m_hConsole == NULL )
			return;
		::FreeConsole();
		m_hConsole = NULL;
		m_hWindow = NULL;
	}
	
	// show
	void SystemConsole::Show( void )
	{
		LockGuard<ThreadLock> s_consoleLock( m_lock );

		if( m_hWindow == NULL )
		{
			TRACE_ERROR( _S("SystemConsole::Show: Console is not created.") );
		}
		else
		{
			::ShowWindow( m_hWindow , SW_SHOW );
		}
	}

	// hide
	void SystemConsole::Hide( void )
	{
		LockGuard<ThreadLock> s_consoleLock( m_lock );

		if( m_hWindow == NULL )
		{
			TRACE_ERROR( _S("SystemConsole::Hide: Console is not created.") );
		}
		else
		{
			::ShowWindow( m_hWindow , SW_HIDE );
		}
	}

	// SetTitle
	void SystemConsole::SetTitle( const WString& title )
	{
		if( ::SetConsoleTitle( title.c_str() ) == FALSE )
		{
			__win32_error( _S("SystemConsole::SetTitle: Failed to set console title: %s.") );
		}
	}

	// show cursor
	void SystemConsole::ShowCursor( void )
	{
		LockGuard<ThreadLock> s_consoleLock( m_lock );

		if( m_hConsole == NULL )
		{
			TRACE_ERROR( _S("SystemConsole::ShowCursor: Console is not created.") );
		}
		else
		{
			CONSOLE_CURSOR_INFO ci;
			ci.dwSize = 1;
			ci.bVisible = TRUE;
			::SetConsoleCursorInfo( m_hConsole , &ci );
		}
	}

	// hide cursor
	void SystemConsole::HideCursor( void )
	{
		LockGuard<ThreadLock> s_consoleLock( m_lock );

		if( m_hConsole == NULL )
		{
			TRACE_ERROR( _S("SystemConsole::HideCursor: Console is not created.") );
		}
		else
		{
			CONSOLE_CURSOR_INFO ci;
			ci.dwSize = 1;
			ci.bVisible = FALSE;
			::SetConsoleCursorInfo( m_hConsole , &ci );
		}
	}

	// get foreground color
	static inline SHORT get_fg_color( SystemConsole::ConsoleColor color )
	{
		static const SHORT s_fg_color_table [] =
		{
			0 ,
			FOREGROUND_INTENSITY | FOREGROUND_RED ,
			FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ,
			FOREGROUND_INTENSITY | FOREGROUND_GREEN ,
			FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE ,
			FOREGROUND_INTENSITY | FOREGROUND_BLUE ,
			FOREGROUND_INTENSITY | FOREGROUND_BLUE | FOREGROUND_RED ,
			FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE ,
			FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE ,
			FOREGROUND_RED ,
			FOREGROUND_RED | FOREGROUND_GREEN ,
			FOREGROUND_GREEN ,
			FOREGROUND_GREEN | FOREGROUND_BLUE ,
			FOREGROUND_BLUE ,
			FOREGROUND_BLUE | FOREGROUND_RED ,
		};
		assert( color >= 0 && color < SystemConsole::CONSOLE_COLOR_last );
		return s_fg_color_table[ color ];
	}

	// get background color
	static inline SHORT get_bg_color( SystemConsole::ConsoleColor color )
	{
		static const SHORT s_bg_color_table [] =
		{
			0 ,
			BACKGROUND_INTENSITY | BACKGROUND_RED ,
			BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN ,
			BACKGROUND_INTENSITY | BACKGROUND_GREEN ,
			BACKGROUND_INTENSITY | BACKGROUND_GREEN | BACKGROUND_BLUE ,
			BACKGROUND_INTENSITY | BACKGROUND_BLUE ,
			BACKGROUND_INTENSITY | BACKGROUND_BLUE | BACKGROUND_RED ,
			BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE ,
			BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE ,
			BACKGROUND_RED ,
			BACKGROUND_RED | BACKGROUND_GREEN ,
			BACKGROUND_GREEN ,
			BACKGROUND_GREEN | BACKGROUND_BLUE ,
			BACKGROUND_BLUE ,
			BACKGROUND_BLUE | BACKGROUND_RED ,
		};
		assert( color >= 0 && color < SystemConsole::CONSOLE_COLOR_last );
		return s_bg_color_table[ color ];
	}

	static const SHORT FG_COLOR = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
	static const SHORT BG_COLOR = BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;

	typedef SystemConsole::ConsoleColor ConsoleColor;
	
	// change color
	static inline void change_fg_color( CHAR_INFO& info , ConsoleColor fg )
	{
		info.Attributes |= ( info.Attributes & ~FG_COLOR ) | get_fg_color( fg );
	}
	static inline void change_bg_color( CHAR_INFO& info , ConsoleColor bg )
	{
		info.Attributes |= ( info.Attributes & ~BG_COLOR ) | get_fg_color( bg );
	}

	// print string x line
	void SystemConsole::PrintLine( int line , int start , const WString& str , ConsoleColor fg , ConsoleColor bg )
	{
		LockGuard<ThreadLock> s_consoleLock( m_lock );

		// change buffer
		if( line >= m_height )
		{
			TRACE_ERROR( _S("SystemConsole::PrintLine: Line pos larger than buffer.") );
			return;
		}

		if( start >= m_width )
		{
			TRACE_ERROR( _S("SystemConsole::PrintLine: Column pos larger than buffer.") );
			return;
		}

		CHAR_INFO * sl = m_buffer + line * m_width;

		// set foreground color
		if( fg != CURRENT )
			for( int i = start ; i < m_width ; ++i )
				change_fg_color( sl[i] , fg );
		if( bg != CURRENT )
			for( int i = start ; i < m_width ; ++i )
				change_bg_color( sl[i] , bg );

		for( int i = start ; i < m_width ; ++i )
		{
			if( (int)str.size() > i )
			{
				sl[i].Char.UnicodeChar = str[i - start];
			}
		}

		COORD size = { m_width , 1 };
		COORD coord = { 0 , 0 };
		SMALL_RECT region = { start , line , m_width , line + 1 };
		if( !::WriteConsoleOutput(
			m_hConsole ,
			sl ,
			size ,
			coord ,
			&region ) )
		{
			::FreeConsole();
			m_hConsole = NULL;
			__win32_error( _S("SystemConsole::PrintLine: Failed to write console : %s.") );
			return;
		}
	}

} // namespace xeres
